﻿using DevExpress.Blazor;
using DevExpress.Data.Filtering;
using DevExpress.Data.Summary;
using DevExpress.ExpressApp.Blazor;
using DevExpress.ExpressApp.Blazor.Components;
using DevExpress.ExpressApp.Blazor.Components.Models;
using DevExpress.ExpressApp.Blazor.Editors;
using DevExpress.ExpressApp.Blazor.Editors.ActionControls;
using DevExpress.ExpressApp.Blazor.Editors.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System.Reflection;

namespace EasyXaf.TreeListEditors.Blazor;

public sealed class TreeListAdapter : DxGridAdapter, IDxGridAdapter, IComponentContentHolder, IDxGridInstanceProvider, ISummaryItemsOwner, ISupportInlineActions
{
    private readonly FieldInfo _componentContentField = typeof(DxGridAdapter).GetField("componentContent", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly FieldInfo _gridSelectionColumnField = typeof(DxGridAdapter).GetField("gridSelectionColumn", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly FieldInfo _cachedCriteriaField = typeof(DxGridAdapter).GetField("cachedCriteria", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly FieldInfo _pendingObjectsToSelectField = typeof(DxGridAdapter).GetField("pendingObjectsToSelect", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly FieldInfo _componentCapturedField = typeof(DxGridAdapter).GetField("componentCaptured", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly FieldInfo _notifierField = typeof(DxGridAdapter).GetField("notifier", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly PropertyInfo _isSplitViewModeProperty = typeof(DxGridAdapter).GetProperty("IsSplitViewMode", BindingFlags.NonPublic | BindingFlags.Instance);
    private readonly MethodInfo _onCustomizeElementMethod = typeof(DxGridAdapter).GetMethod("OnCustomizeElement", BindingFlags.NonPublic | BindingFlags.Instance);

    private readonly PropertyInfo _rowClickProperty;
    private readonly PropertyInfo _rowDblClickProperty;
    private readonly PropertyInfo _rowClickModeProperty;
    private readonly MethodInfo _selectObjectsMethod;

    private readonly Type IDxGridSelectionOperationsType = typeof(IDxGridAdapter).Assembly.GetTypes().First(x => x.Name == "IDxGridSelectionOperations");
    private readonly Type IDxGridRowClickSetupType = typeof(IDxGridAdapter).Assembly.GetTypes().First(x => x.Name == "IDxGridRowClickSetup");

    public DxTreeList TreeList { get; private set; }

    public EventCallback<TreeListRowClickEventArgs> RowClickEventCallback { get; }

    public RenderFragment ComponentContent
    {
        get => (RenderFragment)_componentCapturedField.GetValue(this);
        set => _componentContentField.SetValue(this, value);
    }

    public RenderFragment GridSelectionColumn
    {
        get => (RenderFragment)_gridSelectionColumnField.GetValue(this);
        set => _gridSelectionColumnField.SetValue(this, value);
    }

    public new DxGridSelectionColumnModel GridSelectionColumnModel { get; } = new TreeListSelectionColumnModel
    {
        VisibleIndex = 0,
        Width = "40px",
        ShowInColumnChooser = false
    };

    public Func<object, Task> RowClick
    {
        get => (Func<object, Task>)_rowClickProperty.GetValue(this);
        set => _rowClickProperty.SetValue(this, value);
    }

    public Func<object, Task> RowDblClick
    {
        get => (Func<object, Task>)_rowDblClickProperty.GetValue(this);
        set => _rowDblClickProperty.SetValue(this, value);
    }

    public RowClickMode RowClickMode
    {
        get => (RowClickMode)_rowClickModeProperty.GetValue(this);
        set => _rowClickModeProperty.SetValue(this, value);
    }

    public bool IsSplitViewMode
    {
        get => (bool)_isSplitViewModeProperty.GetValue(this);
    }

    public CriteriaOperator CachedCriteria
    {
        get => (CriteriaOperator)_cachedCriteriaField.GetValue(this);
    }

    public List<object> PendingObjectsToSelect
    {
        get => (List<object>)_pendingObjectsToSelectField.GetValue(this);
    }

    public new EventHandler<ComponentCapturedEventArgs> ComponentCaptured
    {
        get => (EventHandler<ComponentCapturedEventArgs>)_componentCapturedField.GetValue(this);
    }

    public TreeListAdapter(TreeListModel treeListModel)
        : base(treeListModel)
    {
        _rowClickProperty = IDxGridRowClickSetupType.GetProperty("RowClick");
        _rowDblClickProperty = IDxGridRowClickSetupType.GetProperty("RowDblClick");
        _rowClickModeProperty = IDxGridRowClickSetupType.GetProperty("RowClickMode");
        _selectObjectsMethod = IDxGridSelectionOperationsType.GetMethod("SelectObjects");

        GridSelectionColumn = GridSelectionColumnModel.GetComponentContent();
        RowClickEventCallback = EventCallback.Factory.Create(this, async (TreeListRowClickEventArgs args) =>
        {
            if (args.Column is not DxTreeListSelectionColumn)
            {
                if (RowClickMode == RowClickMode.ProcessOnSingle && RowClick != null)
                {
                    await RowClick(args.TreeList.GetDataItem(args.VisibleIndex));
                }
                else if (RowDblClick != null)
                {
                    await RowDblClick(args.TreeList.GetDataItem(args.VisibleIndex));
                }
            }
        });

        treeListModel.CustomizeElement = OnCustomizeElement;
        treeListModel.AllowSelectRowByClick = RowClickMode == RowClickMode.SelectOnSingleProcessOnDouble || IsSplitViewMode;

        if (RowClickMode == RowClickMode.ProcessOnSingle)
        {
            treeListModel.RowClick = RowClickEventCallback;
            treeListModel.RowDoubleClick = default;
            treeListModel.AllowSelectRowByClick = false;
        }
        else
        {
            treeListModel.RowDoubleClick = RowClickEventCallback;
            treeListModel.RowClick = default;
        }
    }

    private void SelectObjects(List<object> objects)
    {
        _selectObjectsMethod.Invoke(this, [objects]);
    }

    private void AddComponentReferenceCapture(object instance)
    {
        TreeList = (DxTreeList)instance;

        if (PendingObjectsToSelect.Count > 0)
        {
            SelectObjects([.. PendingObjectsToSelect]);
        }

        if (CachedCriteria is not null)
        {
            TreeList.SetFilterCriteria(CachedCriteria);
        }

        PendingObjectsToSelect.Clear();
        ComponentCaptured?.Invoke(this, new ComponentCapturedEventArgs(TreeList));
    }

    private RenderFragment CreateComponent()
    {
        var notifier = (IComponentModel)_notifierField.GetValue(this);

        return builder =>
        {
            builder.OpenElement(0, "xaf-contextmenu-click-helper");
            builder.AddContent(1, ComponentModelObserver.Create(notifier, GridModel.GetComponentContent(AddComponentReferenceCapture)));

            if (RowContextMenuAdapter != null)
            {
                builder.AddContent(2, RowContextMenuAdapter.GetComponentContent());
            }

            if (ColumnContextMenuAdapter != null)
            {
                builder.AddContent(3, ColumnContextMenuAdapter.GetComponentContent());
            }

            builder.CloseElement();
        };
    }

    private void OnCustomizeElement(TreeListCustomizeElementEventArgs args)
    {
        if (args.ElementType == TreeListElementType.DataRow)
        {
            var elementType = (GridElementType)args.ElementType;
            var gridCustomizeElementConstructor = typeof(GridCustomizeElementEventArgs).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First();
            var gridCustomizeElement = (GridCustomizeElementEventArgs)gridCustomizeElementConstructor.Invoke([args.TreeList, elementType, args.VisibleIndex, args.Column as IGridColumn]);

            _onCustomizeElementMethod.Invoke(this, [gridCustomizeElement]);

            args.CssClass = gridCustomizeElement.CssClass;
            args.Attributes["oncontextmenu"] = EventCallback.Factory.Create(this, (MouseEventArgs mouseEventArgs) =>
            {
                if (!TreeList.IsRowSelected(args.VisibleIndex))
                {
                    TreeList.ClearSelection();
                    TreeList.SelectRow(args.VisibleIndex);
                }
                return RowContextMenuAdapter.Invoke(mouseEventArgs);
            });
        }
    }

    IGrid IDxGridInstanceProvider.GridInstance => TreeList;

    DxGridSelectionColumnModel IDxGridAdapter.GridSelectionColumnModel => GridSelectionColumnModel;

    RenderFragment IComponentContentHolder.ComponentContent
    {
        get
        {
            return ComponentContent ??= builder =>
            {
                builder.OpenElement(0, "div");
                builder.AddAttribute(1, "class", "grid-content");
                builder.AddContent(2, CreateComponent());
                builder.CloseElement();
            };
        }
    }

    ISummaryItem ISummaryItemsOwner.CreateItem(string fieldName, DevExpress.Data.SummaryItemType summaryType)
    {
        var treeListSummaryItemModel = new TreeListSummaryItemModel
        {
            FieldName = fieldName,
            SummaryType = (GridSummaryItemType)summaryType
        };
        return new DxGridSummaryItemWrapper(treeListSummaryItemModel);
    }

    void ISupportInlineActions.SetupActionColumn(ListEditorInlineActionControlContainer actionContainer)
    {
    }
}
